/******************************************************
 * @file mqttclient.c
 * @author Destiny 
 * @brief mqtt相关，链接MQTT服务器，上报本地信息，并订阅。
 * 
 * @version 0.1
 * @date 2024-07-18
 *****************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/ioctl.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/time.h>
#include <signal.h>
#include <time.h>
#include <MQTTClient.h>
#include "mqttclient.h"
#include "config.h"

#define CLOSE_CMD (_IO(0XEF, 0x1))     /* 关闭定时器 */
#define OPEN_CMD (_IO(0XEF, 0x2))      /* 打开定时器 */
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 设置定时器周期命令 */

static int g_iFdLed, g_iFdDht11;
static MQTTClient Client;
/*
 * @description		    	: 回调函数 mqtt收到订阅消息时调用
 * @param - context	        : 应用程序上下文，通常在调用 MQTTClient_setCallbacks 函数时设置，并在回调函数中传递给应用程序。
 * @param - topicName	    : 指向包含消息主题名称的字符串的指针。
 * @param - topicLen	    : 消息主题名称的长度。
 * @param - message 	    : 指向 MQTTClient_message 结构的指针，该结构包含了接收到的消息内容和相关属性。
 * @return 			    	: 0 成功;其他 失败
 */
static int MessageRecv(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
    int data;
    if (!strcmp(topicName, LED_TOPIC))
    {
        if (!strcmp("0", message->payload))
        {
            data = 0;
            write(g_iFdLed, &data, sizeof(data));
            ioctl(g_iFdLed, CLOSE_CMD, 500);
        }
        else if (!strcmp("1", message->payload))
        {
            data = 1;
            write(g_iFdLed, &data, sizeof(data));
            ioctl(g_iFdLed, CLOSE_CMD, 500);
        }
        else if (!strcmp("2", message->payload))
        {
            ioctl(g_iFdLed, SETPERIOD_CMD, 500);
        }
    }
    MQTTClient_freeMessage(&message);
    MQTTClient_free(topicName);
    /* 退出 */
    return 0;
}
/*
 * @description		    	: 链接断开时调用
 * @param - context	        : 应用程序上下文，通常在调用 MQTTClient_setCallbacks 函数时设置，并在回调函数中传递给应用程序。
 * @param - cause   	    : 连接断开的原因，以字符串形式表示。
 * @return 			    	: 无
 */
static void ConnectLost(void *context, char *cause)
{
    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"\nConnection lost\n");
    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG," cause: %s\n", cause);
}
/*
 * @description		    	: 定时器中断处理函数，每5秒触发一次，上报温湿度数据
 * @param - iSigno	        : SIGALRM信号
 * @return 			    	: 无
 */
static void MqttPublishMessage(int iSigno)
{
    int iError, iCheckNum;
    int humidity, temperature;
    char pcMessage[64];
    MQTTClient_message TempMessage = MQTTClient_message_initializer;

    uint8_t ucTempData[5] = {0};
    iError = read(g_iFdDht11, ucTempData, sizeof(ucTempData));
    // if (iError != sizeof(ucTempData))
    // {
    //     DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"read dht11 error! read num:%d\r\n", iError);
    //     return;
    // }
    /*验证校验和*/
    iCheckNum = ucTempData[0] + ucTempData[1] + ucTempData[2] + ucTempData[3];
    if (iCheckNum != ucTempData[4])
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"checksum is error!\r\n");
        return;
    }
    // 提取湿度和温度值
    humidity = (int)ucTempData[0];
    temperature = (int)ucTempData[2];
    sprintf(pcMessage, "humidity:%d  temperature:%d", humidity, temperature);
    pcMessage[63] = '\0';
    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"%s\r\n",pcMessage);
    /* 发布温度信息 */
    TempMessage.payload = pcMessage;            //消息的内容
    TempMessage.payloadlen = strlen(pcMessage); //内容的长度
    TempMessage.qos = 1;                        //QoS 等级
    TempMessage.retained = 1;                   //保留消息
    iError = MQTTClient_publishMessage(Client, DHT11_TOPIC, &TempMessage, NULL);
    if (MQTTCLIENT_SUCCESS != iError)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to publish message, return code %d\n", iError);
        iError = EXIT_FAILURE;
        return;
    }
}
/*
 * @description		    	: mqtt客户端初始化
 * @param -     	        : 无
 * @return 			    	: 0 成功;其他 失败
 */
static int MqttClienrInit()
{
    MQTTClient_connectOptions ConnectOpts = MQTTClient_connectOptions_initializer;
    MQTTClient_willOptions WillOpts = MQTTClient_willOptions_initializer;
    MQTTClient_message PublishMessage = MQTTClient_message_initializer;
    int iError;
    struct itimerval tTimer;
    /* 创建 mqtt 客户端对象 */
    iError = MQTTClient_create(&Client, BROKER_ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);
    if (MQTTCLIENT_SUCCESS != iError)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to create client, return code %d\n", iError);
        iError = EXIT_FAILURE;
        goto exit;
    }
    /* 设置回调函数 */
    iError = MQTTClient_setCallbacks(Client, NULL, ConnectLost, MessageRecv, NULL);
    if (MQTTCLIENT_SUCCESS != iError)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to set callbacks, return code %d\n", iError);
        iError = EXIT_FAILURE;
        goto destroy_exit;
    }
    /* 连接 MQTT 服务器 */
    WillOpts.topicName = WILL_TOPIC;               //遗嘱主题
    WillOpts.message = "Unexpected disconnection"; //遗嘱消息
    WillOpts.retained = 1;                         //保留消息
    WillOpts.qos = 0;                              //QoS0
    ConnectOpts.will = &WillOpts;
    ConnectOpts.keepAliveInterval = 30; //心跳包间隔时间
    ConnectOpts.cleansession = 0;       //cleanSession 标志
    // conn_opts.username = USERNAME; //用户名
    // conn_opts.password = PASSWORD; //密码
    iError = MQTTClient_connect(Client, &ConnectOpts);
    if (MQTTCLIENT_SUCCESS != iError)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to connect, return code %d\n", iError);
        iError = EXIT_FAILURE;
        goto destroy_exit;
    }
    DBG_PRINTF(DLOG_LVL_INFO,DLOG_TAG,"MQTT 服务器连接成功!\n");
    /* 发布上线消息 */
    PublishMessage.payload = "Online"; //消息的内容
    PublishMessage.payloadlen = 6;     //内容的长度
    PublishMessage.qos = 0;            //QoS 等级
    PublishMessage.retained = 1;       //保留消息
    iError = MQTTClient_publishMessage(Client, WILL_TOPIC, &PublishMessage, NULL);
    if (MQTTCLIENT_SUCCESS != iError)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to publish message, return code %d\n", iError);
        iError = EXIT_FAILURE;
        goto disconnect_exit;
    }
    /* 订阅主题 d1023/led */
    iError = MQTTClient_subscribe(Client, LED_TOPIC, 0);
    if (MQTTCLIENT_SUCCESS != iError)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to subscribe, return code %d\n", iError);
        iError = EXIT_FAILURE;
        goto disconnect_exit;
    }
    /*打开设备节点*/
    g_iFdDht11 = open(DHT11_DEVICE_PATH, O_RDONLY);
    if (g_iFdDht11 < 0)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to open %s", DHT11_DEVICE_PATH);
        goto Dht11_exit;
    }
    g_iFdLed = open(LED_DEVICE_PATH, O_RDWR);
    if (g_iFdLed < 0)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to open %s", LED_DEVICE_PATH);
        goto led_exit;
    }
    /*设置定时器，周期上报*/
    if (signal(SIGALRM, MqttPublishMessage) == SIG_ERR) // 设置定时器处理函数
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to set timer");
    }
    tTimer.it_value.tv_sec = 5; //设置定时间隔为5s
    tTimer.it_value.tv_usec = 0;
    tTimer.it_interval.tv_sec = 5;
    tTimer.it_interval.tv_usec = 0;

    setitimer(ITIMER_REAL, &tTimer, NULL); //启动定时器
    goto exit;
led_exit:
    close(g_iFdDht11);
Dht11_exit:
    iError = MQTTClient_unsubscribe(Client, LED_TOPIC);
    if (MQTTCLIENT_SUCCESS != iError)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to unsubscribe, return code %d\n", iError);
        iError = EXIT_FAILURE;
    }
disconnect_exit:
    iError = MQTTClient_disconnect(Client, 10000);
    if (MQTTCLIENT_SUCCESS != iError)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Failed to disconnect, return code %d\n", iError);
        iError = EXIT_FAILURE;
    }
destroy_exit:
    MQTTClient_destroy(&Client);
exit:
    return iError;
}
/*
 * @description		    	: mqtt服务线程函数
 * @param -     	        : 无
 * @return 			    	: 无
 */
void *Mqtt(void *pVoid)
{
    int iError;
    iError = MqttClienrInit();
    if (iError)
    {
        goto error;
    }
    while (1)
    {
        sleep(30);
    }
error:
    return 0;
}
